package org.zkoss.zktc.core.widget.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement;
import org.zkoss.zktc.core.widget.Widget;
import org.zkoss.zktc.core.widget.WidgetDriver;

public class WidgetImpl implements Widget{

	final protected WidgetDriverImpl driver;
	final protected boolean autoReattach;
	
	private String gadgetId ;
	
	public WidgetImpl(WidgetDriverImpl driver, String gadgetId, boolean autoReattach) {
		this.driver = driver;
		this.gadgetId = gadgetId;
		this.autoReattach = autoReattach;
	}
	
	public String getGadgetId(){
		return gadgetId;
	}
	
	public WidgetDriver getDriver(){
		return driver;
	}
	
	public WebElement getDom(){
		WebElement we = (WebElement)driver.executeWidgetFns("getDom", this);
		return we;
	}
	
	public WebElement getRealDom(){
		WebElement we = (WebElement)driver.executeWidgetFns("getRealElement", this);
		return we;	
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((gadgetId == null) ? 0 : gadgetId.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		WidgetImpl other = (WidgetImpl) obj;
		if (gadgetId == null) {
			if (other.gadgetId != null)
				return false;
		} else if (!gadgetId.equals(other.gadgetId))
			return false;
		return true;
	}

	public String getId() {
		return (String)driver.executeWidgetFns("getId", this);
	}

	public String getUuid() {
		return (String)getAttribute("uuid");
	}
	
	public WebElement findElement(String selector) {
		return findElement(selector, driver.getTimeout());
	}

	public WebElement findElement(String selector, long timeout) {
		return driver.findElement(this,selector,timeout);
	}
	
	public List<WebElement> findElements(String selector) {
		return findElements(selector,driver.getTimeout());
	}
	public List<WebElement> findElements(String selector, long timeout) {
		return driver.findElements(this,selector,timeout);
	}
	
	public Widget findWidget(String selector) {
		return findWidget(selector, false);
	}

	public Widget findWidget(String selector, long timeout) {
		return findWidget(selector, false, timeout);
	}

	public Widget findWidget(String selector, boolean strict) {
		return findWidget(selector,strict,driver.getTimeout());
	}
	public Widget findWidget(String selector, boolean strict, long timeout) {
		return driver.findWidget(this,selector,strict,timeout);
	}
	
	public List<Widget> findWidgets(String selector) {
		return findWidgets(selector, false);
	}

	public List<Widget> findWidgets(String selector, boolean strict) {
		return findWidgets(selector,strict,driver.getTimeout());
	}
	public List<Widget> findWidgets(String selector, boolean strict, long timeout) {
		return driver.findWidgets(this,selector,strict,timeout);
	}
	
	
	public WidgetImpl hide() {
		return hide(1,driver.getTimeout());
	}
	public WidgetImpl hide(int trip,long timeout) {
		long tid = (Long)driver.executeWidgetFns("doHide", this);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}

	public WidgetImpl show() {
		return show(1,driver.getTimeout());
	}
	public WidgetImpl show(int trip,long timeout) {
		long tid = (Long)driver.executeWidgetFns("doShow", this);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}

	public Widget click() {
		return click(1,driver.getTimeout());
	}
	public Widget click(int trip,long timeout) {

//		long tid = (Long)driver.executeWidgetFns("doClick", this);
		//use native selenium click
		long tid = trip<=0?0:driver.getTripId();
		getRealDom().click();
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}

	public Widget dbclick() {
		return dbclick(1,driver.getTimeout());
	}
	public Widget dbclick(int trip,long timeout) {
		long tid = (Long)driver.executeWidgetFns("doDoubleClick", this);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}

	public Widget rclick() {
		return rclick(1,driver.getTimeout());
	}
	public Widget rclick(int trip,long timeout) {
		long tid = (Long) driver.executeWidgetFns("doRightClick", this);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}
	
	/**
	 * Send tab has issue in selenium2 in some os,
	 * http://code.google.com/p/selenium/issues/detail?id=368
	 */
	public Widget tab() {
		return tab(1,driver.getTimeout());
	}
	public Widget tab(int trip,long timeout) {
		long tid = trip<=0?0:driver.getTripId();
		getRealDom().sendKeys(Keys.TAB);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}
	
	public Widget keys(CharSequence text) {
		return keys(text, 0,driver.getTimeout());
	}
	public Widget keys(CharSequence text, int trip,long timeout) {
		long tid = trip<=0?0:trip<=0?0:driver.getTripId();
		getRealDom().sendKeys(text);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}
	
	public Widget replace(CharSequence text) {
		return replace(text, 0,driver.getTimeout());
	}
	public Widget replace(CharSequence text, int trip,long timeout) {
		long tid = trip<=0?0:trip<=0?0:driver.getTripId();
		WebElement we = getRealDom();
		we.clear();
		we.sendKeys(text);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}
	
	public Widget focus() {
		return focus(1,driver.getTimeout());
	}
	public Widget focus(int trip,long timeout) {
		long focusimeout = 0;
		long tid = (Long) driver.executeWidgetFns("doFocus", this,focusimeout);
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}
	
	public Widget clear() {
		return clear(0,driver.getTimeout());
	}
	public Widget clear(int trip,long timeout) {
		long tid = trip<=0?0:driver.getTripId();
		getRealDom().clear();
		driver.waitForTrip(tid,trip, timeout);
		return this;
	}

	public Object getAttribute(String name) {
		return driver.executeWidgetFns("getAttribute", this, name);	
	}
	
	/** TODO spec of value **/
	public void setAttribute(String name,Object value) {
		driver.executeWidgetFns("setAttribute", this, name,value);	
	}
	
	/** TODO spec of parameters **/
	public Object call(String methodName,Object... parameters) {
		Object[] ps = new Object[1 + (parameters == null ? 0 : parameters.length)];
		ps[0] = methodName;
		if (parameters != null) {
			System.arraycopy(parameters, 0, ps, 1, parameters.length);
		}
		Object obj = driver.executeWidgetFns("callFunction", this, ps);
		return obj;
	}
	private static final String GET_PARENT = "getParent";
	
	public Widget getParent() {
		String wid = (String)driver.executeWidgetFns(GET_PARENT, this,0);
		return driver.toWidgetImpl(wid);
	}

	public Widget getParent(int height) {
		String wid = (String)driver.executeWidgetFns(GET_PARENT, this,height);
		return driver.toWidgetImpl(wid);
	}
	private static final String GET_CHILDREN = "getChildren";
	
	public List<Widget> getChildren() {
		long t1 = System.currentTimeMillis();
		List r = null;
//		while (r == null || r.size() == 0) {
		r = (List)driver.executeWidgetFns(GET_CHILDREN, this);
//			if (r == null || r.size() == 0) {
//				if (timeout<=0 || System.currentTimeMillis() - t1 >= timeout)
//					break;
//				try {
//					Thread.sleep(100);
//				} catch (InterruptedException e) {
//				}
//			}
//		}
		if(r==null || r.size()==0) return Collections.EMPTY_LIST;
		List<Widget> l = new ArrayList<Widget>();
		for (Object o : (List) r) {
			l.add(driver.toWidgetImpl((String)o));
		}
		return l;
	}
	
	private static final String GET_WIDGETATTR = "getWidgetAttribute";
	public Widget getNextSibling() {
		String wid = (String)driver.executeWidgetFns(GET_WIDGETATTR, this,"nextSibling");
		return driver.toWidgetImpl(wid);
	}
	
	public Widget getPreviousSibling() {
		String wid = (String)driver.executeWidgetFns(GET_WIDGETATTR, this,"previousSibling");
		return driver.toWidgetImpl(wid);
	}
	
	public Widget getFirstChild() {
		String wid = (String)driver.executeWidgetFns(GET_WIDGETATTR, this,"firstChild");
		return driver.toWidgetImpl(wid);
	}
	
	public Widget getLastChild() {
		String wid = (String)driver.executeWidgetFns(GET_WIDGETATTR, this,"lastChild");
		return driver.toWidgetImpl(wid);
	}
	

	public Widget getWidgetAttribute(String name) {
		String wid = (String)driver.executeWidgetFns(GET_WIDGETATTR, this,name);
		return wid==null?null:driver.toWidgetImpl(wid);
	}
	
	
	private static final String GET_ATTR = "getAttribute";
	private static final String DUMP = "dump";
	
	public int getChildrenSize(){
		Number n = (Number)driver.executeWidgetFns(GET_ATTR, this,"nChildren");
		return n==null?0:n.intValue();
	}
	
	public String getWidgetName(){
		return (String)driver.executeWidgetFns(GET_ATTR, this,"widgetName");
	}
	
	public String getMold(){
		return (String)driver.executeWidgetFns(GET_ATTR, this,"mold");
	}
	
	public String getClassName(){
		return (String)driver.executeWidgetFns(GET_ATTR, this,"className");
	}
	
	public String getLabel(){
		return (String)driver.executeWidgetFns(GET_ATTR, this,"label");
	}
	
	public Object getValue(){
		return driver.executeWidgetFns(GET_ATTR, this,"value");
	}
	
	public String getText(){
		return (String)driver.executeWidgetFns(GET_ATTR, this,"text");
	}

	@Deprecated
	public Widget reattach() {
		//use cahced lastuuid to reattach
//		String selector = new StringBuilder().append("#").append(lastuuid).toString();
//		WebElement d = driver.findElement(selector);
//		if(d!=null){
//			delegate = d;
//		}
		return this;
	}

	public boolean isVisible() {
		Object r = call("isVisible", true);
		return r != null && Boolean.TRUE.equals(r);
	}
	

	
	public void dumpDom(boolean client) {
		
		if(client){
			driver.executeWidgetFns(DUMP, this,true);
		}else{
			StringBuilder sb = new StringBuilder();
			dumpDom(sb,this.getDom(),0);
			System.out.println(sb.toString());
		}
	}


	public void dump(boolean client) {
		if(client){
			driver.executeWidgetFns(DUMP, this);
		}else{
			StringBuilder sb = new StringBuilder();
			dumpWidget(sb,this,0);
			System.out.println(sb.toString());
		}
	}
	
	private void dumpWidget(StringBuilder sb, Widget widget,int indent) {
		List<Widget> children = widget.getChildren();
		StringBuffer idt = new StringBuffer();
		for(int i=0;i<indent;i++){
			idt.append("  ");
		}
		sb.append(idt);
		String nm = widget.getWidgetName(); 
		sb.append("<");
		sb.append(nm);
		sb.append(" uuid=\"").append(widget.getUuid()).append("\"");
		
		String id = widget.getId();
		if(id != null){
			sb.append(" id=\"").append(id).append("\"");
		}
		
		
		Object value = widget.getValue();
		if(value != null){
			sb.append(" value=\"").append(value).append("\"");
		}
		
		String label = widget.getLabel();
		if(label != null){
			sb.append(" label=\"").append(label).append("\"");
		}
		
		String text = widget.getLabel();
		if(text != null && !text.equals(label)){
			sb.append(" text=\"").append(text).append("\"");
		}
		
		if(children.size()>0){
			sb.append(">\n");
		}else{
			sb.append(" />\n");
		}
		
		for(Widget w:children){
			dumpWidget(sb,w,indent+1);
		}
		
		if(children.size()>0){
			sb.append(idt);
			sb.append("</").append(nm).append(">\n");
		}
		
		
	}
	
	private void dumpDom(StringBuilder sb, WebElement dom,int indent) {
		
		
		StringBuffer idt = new StringBuffer();
		for(int i=0;i<indent;i++){
			idt.append("  ");
		}
		sb.append(idt);
		String nm = dom.getTagName();
		
		if("label".equals(nm)){
			sb.append(dom.getText());
			sb.append("\n");
			return;
		}
		
		sb.append("<");
		sb.append(nm);
		
		String id = dom.getAttribute("id");
		if(id!=null){
			sb.append(" id=\"").append(id).append("\"");
		}
		
		String clazz = dom.getAttribute("class");
		if(!isEmpty(clazz)){
			sb.append(" class=\"").append(clazz).append("\"");
		}
		
		String style = dom.getAttribute("style");
		if(!isEmpty(style)){
			sb.append(" style=\"").append(style).append("\"");
		}
		
		List<WebElement> children = (List<WebElement>)driver.executeFns("getChildrenElement", dom);
		
		if(children.size()>0){
			sb.append(">\n");
		}else{
			sb.append(" />\n");
		}
		
		for(WebElement d:children){
			dumpDom(sb, d,indent+1);
		}
		
		if(children.size()>0){
			sb.append(idt);
			sb.append("</").append(nm).append(">\n");
		}
	}
	
	private boolean isEmpty(String str){
		return str==null || "".equals(str.trim());
	}


}
